package laboratoryQ.ui.draw;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.text.DecimalFormat;

import laboratoryQ.flatness.FlatnessUtils;
import laboratoryQ.flatness.ICoordinateTransformer;
import laboratoryQ.flatness.Point;
import laboratoryQ.flatness.RangeInfo;
import laboratoryQ.ui.draw.style.IStylizer;

public class AxesDrawer implements IDrawer {
	private ICoordinateTransformer transformer;
	private RangeInfo rangeInfo;
	private Point axesBegin;
	
	private double dirLeng = 10.0;
	private double angle = Math.PI * 30.0 / 180.0;
	
	private String xCaption = "X";
	private String yCaption = "Y";
	
	private double[] scaleX;
	private double[] scaleY;
	private double[] scaleXBuf;
	private double[] scaleYBuf;
	private double[] tmpBufX;
	private double[] tmpBufY;
	private double scaleLeng = 10.0;
	private Point scaleStep;
	private DecimalFormat formatterX = new DecimalFormat();
	private DecimalFormat formatterY = formatterX;
	
	private IDrawStrategy drawStrategy = IDrawStrategy.DefaultDrawStrategy.INSTANCE;
	
	public AxesDrawer(
			ICoordinateTransformer transformer,
			RangeInfo rangeInfo,
			Point axesBegin,
			Point scaleStep
	) {
		this.transformer = transformer;
		this.axesBegin = axesBegin;
		this.rangeInfo = rangeInfo;
		this.scaleStep = scaleStep;
		generateScales();
		formatterX.setMaximumFractionDigits(1);
	}
	
	private void generateScales() {
		int totalX = (int) ((rangeInfo.maxX - rangeInfo.minX) / scaleStep.x);
		int totalY = (int) ((rangeInfo.maxY - rangeInfo.minY) / scaleStep.y);
		tmpBufX = new double[totalX];
		tmpBufY = new double[totalY];
		scaleX = new double[totalX];
		scaleXBuf = new double[totalX];
		scaleY = new double[totalY];
		scaleYBuf = new double[totalY];
		generateScales(
				rangeInfo.minX, 
				rangeInfo.maxX, 
				axesBegin.x, 
				scaleStep.x, 
				scaleX
		);
		generateScales(
				rangeInfo.minY, 
				rangeInfo.maxY, 
				axesBegin.y, 
				scaleStep.y, 
				scaleY);
	}

	private static void generateScales(
			double rbeg,
			double rend,
			double rzero,
			double step,
			double[] out
	) {
		int preX = (int)((rzero - rbeg) / step);
		int total = (int)((rend - rbeg) / step);
		double cur = rzero - preX * step;
		for (int i = 0; i < total; i++) {
			out[i] = cur;
			cur += step;
		}
	}

	private void drawScales(Graphics g, double x0, double y0) {
		FlatnessUtils.doTransform(
				scaleX, 
				tmpBufX,
				scaleXBuf, 
				tmpBufX,
				transformer, 
				g
		);
		FlatnessUtils.doTransform(
				tmpBufY, 
				scaleY,
				tmpBufY, 
				scaleYBuf,
				transformer, 
				g
		);
		double wLeng = g.getFontMetrics()
			.getStringBounds("W", g).getWidth();
		double textHeight = g.getFontMetrics().getHeight();
		for (int i = 0; i < scaleYBuf.length; i++) {
			double y = scaleYBuf[i];
			drawStrategy.drawLine(g, 
					(int)(x0 - scaleLeng / 2.0),
					(int)(y),
					(int)(x0 + scaleLeng / 2.0),
					(int)(y)
			);
			String valRepr = formatterY.format(scaleY[i]); 
			double testLeng = g.getFontMetrics()
				.getStringBounds(valRepr, g).getWidth();;
			g.drawString(
					valRepr,
					(int)(x0 - testLeng - wLeng),
					(int)(y + textHeight * 0.25)
			);
		}
		for (int i = 0; i < scaleXBuf.length; i++) {
			double x = scaleXBuf[i];
			drawStrategy.drawLine(g, 
					(int)(x),
					(int)(y0 - scaleLeng / 2.0),
					(int)(x),
					(int)(y0 + scaleLeng / 2.0)
			);
			String valRepr = formatterX.format(scaleX[i]); 
			double testLeng = g.getFontMetrics()
				.getStringBounds(valRepr, g).getWidth();;
			g.drawString(
					valRepr,
					(int)(x - testLeng / 2),
					(int)(y0 + textHeight * 1.5)
			);
		}
	}

	@Override
	public void draw(Graphics g, IStylizer stylizer) {
		Font oldFont = g.getFont();
		fontSafeDraw(g);
		g.setFont(oldFont);
	}

	private void fontSafeDraw(Graphics g) {
		g.setColor(Color.BLACK);
		double[] xs = new double[] {rangeInfo.minX, rangeInfo.maxX, axesBegin.x};
		double[] ys = new double[] {rangeInfo.minY, rangeInfo.maxY, axesBegin.y};
		transformer.transform(xs, ys, g);
		drawScales(g, xs[2], ys[2]);
		double textHeight = g.getFontMetrics().getHeight();
		drawStrategy.drawLine(g, 
				(int)xs[0], 
				(int)ys[2], 
				(int)xs[1], 
				(int)ys[2]
		);
		drawStrategy.drawLine(g, 
				(int)xs[1], 
				(int)ys[2],
				(int)(xs[1] + dirLeng * Math.cos(Math.PI - angle)), 
				(int)(ys[2] + dirLeng * Math.sin(Math.PI - angle))
		);
		drawStrategy.drawLine(g, 
				(int)xs[1], 
				(int)ys[2],
				(int)(xs[1] + dirLeng * Math.cos(Math.PI - angle)), 
				(int)(ys[2] - dirLeng * Math.sin(Math.PI - angle))
		);
		double xCapLeng = g.getFontMetrics()
			.getStringBounds(xCaption, g).getBounds2D().getMaxX();
		g.drawString(
				xCaption, 
				(int)(xs[1] - xCapLeng / 2), 
				(int)(ys[2] + textHeight * 1.5)
		);
		
		drawStrategy.drawLine(g, 
				(int)xs[2], 
				(int)ys[0], 
				(int)xs[2], 
				(int)ys[1]
		);
		drawStrategy.drawLine(g, 
				(int)xs[2], 
				(int)ys[1],
				(int)(xs[2] + dirLeng * Math.sin(Math.PI - angle)),
				(int)(ys[1] - dirLeng * Math.cos(Math.PI - angle))
		);
		drawStrategy.drawLine(g, 
				(int)xs[2], 
				(int)ys[1],
				(int)(xs[2] - dirLeng * Math.sin(Math.PI - angle)),
				(int)(ys[1] - dirLeng * Math.cos(Math.PI - angle))
		);
		double wwLeng = g.getFontMetrics()
			.getStringBounds("W", g).getBounds2D().getMaxX();
		g.drawString(
				yCaption, 
				(int)(xs[2] + wwLeng), 
				(int)(ys[1] + textHeight / 2)
		);
	}
	
	public void createExceedBoardsInRangeInfo(double excPart) {
		double h = rangeInfo.maxY - rangeInfo.minY;
		double w = rangeInfo.maxX - rangeInfo.minX;
		h *= excPart;
		w *= excPart;
		double exc = Math.min(h, w);
		exc /= 2.0;
		rangeInfo.minX -= exc;
		rangeInfo.minY -= exc;
		rangeInfo.maxX += exc;
		rangeInfo.maxY += exc;
		generateScales();
	}

	public void setXCaption(String xCaption) {
		this.xCaption = xCaption;
	}

	public void setYCaption(String yCaption) {
		this.yCaption = yCaption;
	}
}
